package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol;
import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
import cn.iocoder.yudao.module.iot.gateway.service.scene.IotSceneService;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotSceneActionDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotSceneRespDTO;
import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * IoT 网关 EMQX 下行消息处理器
 * <p>
 * 从消息总线接收到下行消息，然后发布到 MQTT Broker，从而被设备所接收
 *
 * @author 芋道源码
 */
@Slf4j
public class IotEmqxDownstreamHandler {

    private final IotEmqxUpstreamProtocol protocol;

    private final IotDeviceService deviceService;

    private final IotDeviceMessageService deviceMessageService;

    private final IotSceneService sceneService;

    public IotEmqxDownstreamHandler(IotEmqxUpstreamProtocol protocol) {
        this.protocol = protocol;
        this.deviceService = SpringUtil.getBean(IotDeviceService.class);
        this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
        this.sceneService = SpringUtil.getBean(IotSceneService.class);
    }

    /**
     * 处理下行消息
     *
     * @param message 设备消息
     */
    public void handle(IotDeviceMessage message) {
        // 1. 检查是否为设备发送的原始上行消息（用于场景触发）
        if (IotDeviceMessageUtils.isUpstreamMessage(message) && message.getCode() == null) {
            log.info("[handle][处理设备上行消息，设备ID: {}, 方法: {}, 参数: {}]", 
                message.getDeviceId(), message.getMethod(), message.getParams());
            
            // 1.1 获取设备信息
            IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());
            if (deviceInfo == null) {
                log.error("[handle][设备信息({})不存在]", message.getDeviceId());
                return;
            }

            // 1.2 检查params字段，只处理有数据的上行消息
            Object paramsObj = message.getParams();
            if (paramsObj == null || !(paramsObj instanceof java.util.Map)) {
                log.debug("[handle][跳过处理，params字段为空或不是Map类型，deviceId: {}, method: {}, params: {}]", 
                    message.getDeviceId(), message.getMethod(), paramsObj);
                return;
            }

            // 1.3 获取当前北京时间
            ZonedDateTime beijingTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
            LocalTime currentTime = beijingTime.toLocalTime();
            String currentTimeStr = currentTime.format(DateTimeFormatter.ofPattern("HH:mm"));
            log.info("[handle][当前北京时间: {}]", currentTimeStr);
            
            // 1.4 获取所有启用的场景（包括定时场景）
            java.util.List<IotSceneRespDTO> allScenes = sceneService.getAllEnabledScenes();
            log.info("[handle][设备ID:{} 查询到{}个启用场景]", message.getDeviceId(), allScenes.size());
            
            for (IotSceneRespDTO scene : allScenes) {
                if (scene.getConditions() == null) continue;
                boolean match = false;
                log.info("[handle][检查场景ID:{} 的条件匹配]", scene.getId());
                
                for (var cond : scene.getConditions()) {
                    // 检查设备条件
                    if (cond.getDeviceId() != null && cond.getDeviceId().equals(message.getDeviceId())) {
                        @SuppressWarnings("unchecked")
                        java.util.Map<String, Object> params = (java.util.Map<String, Object>) paramsObj;
                        Object val = params.get(cond.getAttr());
                        
                        log.info("[handle][设备条件检查 - 属性:{} 期望值:{} 实际值:{} 操作符:{}]", 
                            cond.getAttr(), cond.getValue(), val, cond.getOperator());
                        
                        if (val != null && compare(val.toString(), cond.getValue(), cond.getOperator())) {
                            match = true;
                            log.info("[handle][设备条件匹配成功 - 场景ID:{} 属性:{} 期望值:{} 实际值:{}]", 
                                scene.getId(), cond.getAttr(), cond.getValue(), val);
                        }
                    }
                    
                    // 检查定时条件
                    if ("定时".equals(cond.getAttr()) || "timer".equals(cond.getAttr())) {
                        log.info("[handle][定时条件检查 - 期望时间:{} 当前时间:{}]", cond.getValue(), currentTimeStr);
                        
                        // 检查时间是否匹配
                        boolean timeMatch = currentTimeStr.equals(cond.getValue());
                        
                        // 检查周几条件
                        boolean dayMatch = checkWeekDayCondition(cond);
                        
                        if (timeMatch && dayMatch) {
                            match = true;
                            log.info("[handle][定时条件匹配成功 - 场景ID:{} 期望时间:{} 当前时间:{}]", 
                                scene.getId(), cond.getValue(), currentTimeStr);
                        } else {
                            log.debug("[handle][定时条件不匹配 - 场景ID:{} 时间匹配:{} 周几匹配:{}]", 
                                scene.getId(), timeMatch, dayMatch);
                        }
                    }
                    
                    // 检查场景条件
                    if ("SCENE".equals(cond.getAttr()) || "scene".equals(cond.getAttr()) || "场景".equals(cond.getAttr())) {
                        log.info("[handle][场景条件检查 - 条件值:{}]", cond.getConditionValue());
                        try {
                            java.util.Map<String, Object> sceneInfo = null;
                            if (cond.getConditionValue() != null) {
                                try {
                                    String jsonStr = cond.getConditionValue();
                                    if (jsonStr.startsWith("{") && jsonStr.endsWith("}")) {
                                        @SuppressWarnings("unchecked")
                                        java.util.Map<String, Object> parsed = JSONUtil.toBean(jsonStr, java.util.Map.class);
                                        sceneInfo = parsed;
                                    }
                                } catch (Exception e) {
                                    log.warn("[handle][JSON解析失败 - 条件值:{}]", cond.getConditionValue(), e);
                                }
                            }
                            
                            if (sceneInfo != null) {
                                Long targetSceneId = (Long) sceneInfo.get("sceneId");
                                String sceneExecution = (String) sceneInfo.get("sceneExecution");
                                
                                log.info("[handle][场景条件解析 - 目标场景ID:{} 执行状态:{}]", targetSceneId, sceneExecution);
                                
                                // 检查目标场景是否存在
                                IotSceneRespDTO targetScene = sceneService.getSceneFromCache(targetSceneId);
                                if (targetScene != null) {
                                    log.info("[handle][目标场景存在 - 场景ID:{} 场景名称:{}]", 
                                        targetScene.getId(), targetScene.getSceneName());
                                    
                                    // 检查目标场景是否正在执行
                                    boolean isExecuting = checkSceneExecutingStatus(targetSceneId);
                                    
                                    if ("EXECUTE".equals(sceneExecution) && isExecuting) {
                                        match = true;
                                        log.info("[handle][场景条件匹配成功 - 场景ID:{} 目标场景ID:{} 正在执行]", 
                                            scene.getId(), targetSceneId);
                                    } else if ("STOP".equals(sceneExecution) && !isExecuting) {
                                        match = true;
                                        log.info("[handle][场景条件匹配成功 - 场景ID:{} 目标场景ID:{} 已停止]", 
                                            scene.getId(), targetSceneId);
                                    } else {
                                        log.debug("[handle][场景条件不匹配 - 场景ID:{} 目标场景ID:{} 期望执行状态:{} 实际执行状态:{}]", 
                                            scene.getId(), targetSceneId, sceneExecution, isExecuting);
                                    }
                                } else {
                                    log.warn("[handle][目标场景不存在或未启用 - 场景ID:{}]", targetSceneId);
                                }
                            } else {
                                log.warn("[handle][场景条件值解析失败 - 条件值:{}]", cond.getConditionValue());
                            }
                        } catch (Exception e) {
                            log.error("[handle][场景条件处理异常 - 场景ID:{} 条件值:{}]", scene.getId(), cond.getConditionValue(), e);
                        }
                    }
                }
                
                if (match && scene.getActions() != null) {
                    log.info("[handle][场景ID:{} 条件匹配成功，开始执行{}个动作]", scene.getId(), scene.getActions().size());
                    for (var action : scene.getActions()) {
                        log.info("[handle][执行动作 - 场景ID:{} 设备ID:{} 动作类型:{} 动作值:{}]", 
                            scene.getId(), action.getDeviceId(), action.getActionType(), action.getActionValue());
                        sendActionToDevice(action);
                    }
                } else if (match) {
                    log.warn("[handle][场景ID:{} 条件匹配成功，但没有找到动作]", scene.getId());
                } else {
                    log.debug("[handle][场景ID:{} 条件不匹配]", scene.getId());
                }
            }
            return;
        }
        
        // 2. 检查是否为下行消息（设备控制消息）
        if (!IotDeviceMessageUtils.isUpstreamMessage(message)) {
            log.info("[handle][处理下行消息，设备ID: {}, 方法: {}, 参数: {}]", 
                message.getDeviceId(), message.getMethod(), message.getParams());
            
            // 2.1 获取设备信息
            IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());
            if (deviceInfo == null) {
                log.error("[handle][设备信息({})不存在]", message.getDeviceId());
                return;
            }
            
            // 2.2 根据方法构建主题
            String topic = buildTopicByMethod(message, deviceInfo.getProductKey(), deviceInfo.getDeviceName());
            if (CharSequenceUtil.isBlank(topic)) {
                log.warn("[handle][未知的消息方法: {}]", message.getMethod());
                return;
            }
            
            // 2.3 构建载荷
            byte[] payload = deviceMessageService.encodeDeviceMessage(message, deviceInfo.getProductKey(), deviceInfo.getDeviceName());

            
            // 2.4 发布消息到MQTT
            protocol.publishMessage(topic, payload);


            // 2.5 根据方法构建主题
            String webTopic = buildWebTopicByMethod(message, deviceInfo.getProductKey(), deviceInfo.getDeviceName());
            if (CharSequenceUtil.isNotBlank(webTopic) && message.getParams() instanceof LinkedHashMap) {
                // 使用@SuppressWarnings限定在最小范围
                @SuppressWarnings("unchecked")
                LinkedHashMap<String, Object> params = ((LinkedHashMap<String, Object>)message.getParams());
                params.put("topic", webTopic);
                // 2.3 构建载荷
                byte[] webPayload = deviceMessageService.encodeDeviceMessage(message, deviceInfo.getProductKey(), deviceInfo.getDeviceName());
                // 2.6 发布消息到MQTT
                protocol.publishMessage(webTopic, webPayload);
            }


            log.info("[handle][下行消息发送成功，设备ID: {}, 产品Key: {}, 设备名: {}, 主题: {}, 方法: {}]", 
                message.getDeviceId(), deviceInfo.getProductKey(), deviceInfo.getDeviceName(), topic, message.getMethod());
            return;
        }
        
        // 3. 其他消息类型，记录日志但不处理
        log.debug("[handle][跳过处理其他类型消息，设备ID: {}, 方法: {}, code: {}", 
            message.getDeviceId(), message.getMethod(), message.getCode());
    }

    private String buildWebTopicByMethod(IotDeviceMessage message, String productKey, String deviceName) {

        // 1. 解析消息方法
        IotDeviceMessageMethodEnum methodEnum = IotDeviceMessageMethodEnum.of(message.getMethod());
        if (methodEnum == null) {
            log.warn("[buildTopicByMethod][未知的消息方法: {}]", message.getMethod());
            return null;
        }

        // 2. 根据消息方法和回复状态，构建 topic
        boolean isReply = IotDeviceMessageUtils.isReplyMessage(message);

        // TODO @芋艿：需要添加对应的 Topic，所以需要先判断消息方法类型
        // TODO @haohao：基于 method，然后逆推对应的 topic，可以哇？约定好~
        // 根据消息方法和回复状态构建对应的主题
        switch (methodEnum) {
            case PROPERTY_POST:
                if (isReply) {
                    return IotMqttTopicUtils.buildWebPropertyPostReplyTopic(productKey, deviceName);
                }
                break;
        }

        log.warn("[buildTopicByMethod][暂时不支持的下行消息: method={}, isReply={}]",
                message.getMethod(), isReply);
        return null;
    }

    /**
     * 条件对比，支持操作符
     */
    private boolean compare(String actual, String expect, String operator) {
        if (operator == null || operator.equals("=")) {
            // 字符串相等比较
            return actual.equals(expect);
        }
        
        try {
            // 尝试数值比较
            double actualNum = Double.parseDouble(actual);
            double expectNum = Double.parseDouble(expect);
            
            switch (operator) {
                case ">": return actualNum > expectNum;
                case ">=": return actualNum >= expectNum;
                case "<": return actualNum < expectNum;
                case "<=": return actualNum <= expectNum;
                case "!=": return actualNum != expectNum;
                default: return actual.equals(expect);
            }
        } catch (NumberFormatException e) {
            // 如果无法转换为数值，则进行字符串比较
            log.debug("[compare][无法进行数值比较，使用字符串比较 - 实际值:{} 期望值:{} 操作符:{}]", actual, expect, operator);
            switch (operator) {
                case ">": return actual.compareTo(expect) > 0;
                case ">=": return actual.compareTo(expect) >= 0;
                case "<": return actual.compareTo(expect) < 0;
                case "<=": return actual.compareTo(expect) <= 0;
                case "!=": return !actual.equals(expect);
                default: return actual.equals(expect);
            }
        }
    }

    /**
     * 根据消息方法和回复状态构建主题
     *
     * @param message    设备消息
     * @param productKey 产品标识
     * @param deviceName 设备名称
     * @return 构建的主题，如果方法不支持返回 null
     */
    private String buildTopicByMethod(IotDeviceMessage message, String productKey, String deviceName) {
        // 1. 解析消息方法
        IotDeviceMessageMethodEnum methodEnum = IotDeviceMessageMethodEnum.of(message.getMethod());
        if (methodEnum == null) {
            log.warn("[buildTopicByMethod][未知的消息方法: {}]", message.getMethod());
            return null;
        }

        // 2. 根据消息方法和回复状态，构建 topic
        boolean isReply = IotDeviceMessageUtils.isReplyMessage(message);

        // TODO @芋艿：需要添加对应的 Topic，所以需要先判断消息方法类型
        // TODO @haohao：基于 method，然后逆推对应的 topic，可以哇？约定好~
        // 根据消息方法和回复状态构建对应的主题
        switch (methodEnum) {
            case PROPERTY_POST:
                if (isReply) {
                    return IotMqttTopicUtils.buildPropertyPostReplyTopic(productKey, deviceName);
                }
                break;
            case PROPERTY_SET:
                if (!isReply) {
                    return IotMqttTopicUtils.buildIotPropertySetTopic(productKey, deviceName);
                }
                break;
        }

        log.warn("[buildTopicByMethod][暂时不支持的下行消息: method={}, isReply={}]",
                message.getMethod(), isReply);
        return null;
    }

    /**
     * 下发动作指令到设备
     */
    private void sendActionToDevice(IotSceneActionDTO action) {
        try {
            log.info("[sendActionToDevice][开始执行动作 - 设备ID:{} 属性:{} 值:{}]", 
                action.getDeviceId(), action.getAttr(), action.getActionValue());
            
            // 1. 获取目标设备信息
            IotDeviceRespDTO targetDevice = deviceService.getDeviceFromCache(action.getDeviceId());
            if (targetDevice == null) {
                log.error("[sendActionToDevice][目标设备不存在，设备ID:{}]", action.getDeviceId());
                return;
            }
            
            log.info("[sendActionToDevice][获取到目标设备信息 - 设备ID:{} 产品Key:{} 设备名:{}]", 
                targetDevice.getId(), targetDevice.getProductKey(), targetDevice.getDeviceName());
            
            // 2. 构建控制消息
            IotDeviceMessage controlMessage = IotDeviceMessage.requestOf(
                "thing.property.set", // 属性设置方法
                java.util.Map.of(
                    action.getAttr(), action.getActionValue() // 设置属性值
                )
            );
            
            log.info("[sendActionToDevice][构建控制消息成功 - 方法:{} 属性:{} 值:{}]", 
                controlMessage.getMethod(), action.getAttr(), action.getActionValue());
            
            // 3. 构建MQTT主题
            String topic = IotMqttTopicUtils.buildPropertySetTopic(
                targetDevice.getProductKey(), 
                targetDevice.getDeviceName()
            );
            
            log.info("[sendActionToDevice][构建MQTT主题成功 - 主题:{}]", topic);
            
            // 4. 编码消息
            byte[] payload = deviceMessageService.encodeDeviceMessage(
                controlMessage, 
                targetDevice.getProductKey(), 
                targetDevice.getDeviceName()
            );
            
            log.info("[sendActionToDevice][编码消息成功 - 载荷长度:{}]", payload.length);
            
            // 5. 发送到MQTT Broker
            protocol.publishMessage(topic, payload);
            
            log.info("[sendActionToDevice][成功发送控制指令 - 设备ID:{} 产品Key:{} 设备名:{} 属性:{} 值:{} 主题:{}]", 
                action.getDeviceId(), targetDevice.getProductKey(), targetDevice.getDeviceName(), 
                action.getAttr(), action.getActionValue(), topic);

            // 3. 构建MQTT主题
            String topicIot = IotMqttTopicUtils.buildIotPropertySetTopic(
                    targetDevice.getProductKey(),
                    targetDevice.getDeviceName()
            );

            log.info("[sendActionToDevice-iot][构建MQTT主题成功 - 主题:{}]", topicIot);

            log.info("[sendActionToDevice-iot][编码消息成功 - 载荷长度:{}]", payload.length);

            // 5. 发送到MQTT Broker
            protocol.publishMessage(topicIot, payload);

            log.info("[sendActionToDevice-iot][成功发送控制指令 - 设备ID:{} 产品Key:{} 设备名:{} 属性:{} 值:{} 主题:{}]",
                    action.getDeviceId(), targetDevice.getProductKey(), targetDevice.getDeviceName(),
                    action.getAttr(), action.getActionValue(), topicIot);
                
        } catch (Exception e) {
            log.error("[sendActionToDevice][发送控制指令失败 - 设备ID:{} 属性:{} 值:{}]", 
                action.getDeviceId(), action.getAttr(), action.getActionValue(), e);
        }
    }

    /**
     * 检查周几条件
     */
    private boolean checkWeekDayCondition(cn.iocoder.yudao.module.iot.core.biz.dto.IotSceneConditionDTO condition) {
        try {
            if (condition.getConditionValue() == null || condition.getConditionValue().isEmpty()) {
                // 如果没有conditionValue，默认匹配
                return true;
            }
            
            Map<String, Object> conditionValue = JSONUtil.toBean(condition.getConditionValue(), Map.class);
            String executionMode = (String) conditionValue.get("executionMode");
            
            // 单次执行：直接匹配
            if ("once".equals(executionMode)) {
                log.debug("[checkWeekDayCondition][单次执行模式，直接匹配]");
                return true;
            }
            
            // 重复执行：检查周几
            if ("repeat".equals(executionMode)) {
                @SuppressWarnings("unchecked")
                List<Integer> selectedDays = (List<Integer>) conditionValue.get("days");
                if (selectedDays != null && !selectedDays.isEmpty()) {
                    // 获取当前是周几（1-7，1代表周一）
                    int currentDayOfWeek = java.time.LocalDate.now().getDayOfWeek().getValue();
                    
                    boolean dayMatch = selectedDays.contains(currentDayOfWeek);
                    log.debug("[checkWeekDayCondition][重复执行模式，当前周几: {}, 选择的周几: {}, 匹配: {}]", 
                        currentDayOfWeek, selectedDays, dayMatch);
                    return dayMatch;
                }
            }
            
            // 默认匹配（兼容旧数据）
            return true;
        } catch (Exception e) {
            log.warn("[checkWeekDayCondition][检查周几条件失败，条件ID: {}]", condition.getId(), e);
            return true; // 出错时默认匹配
        }
    }
    
    /**
     * 检查场景是否正在执行
     */
    private boolean checkSceneExecutingStatus(Long sceneId) {
        try {
            // 这里可以通过Redis缓存或者数据库查询来检查场景的执行状态
            // 暂时返回false，表示场景未在执行状态
            // TODO: 实现场景执行状态的检查逻辑
            log.debug("[checkSceneExecutingStatus][检查场景执行状态 - 场景ID:{} 当前状态: 未执行]", sceneId);
            return false;
        } catch (Exception e) {
            log.warn("[checkSceneExecutingStatus][检查场景执行状态失败 - 场景ID:{}]", sceneId, e);
            return false;
        }
    }
}